// 第二部分 this和对象原型
// 第三章  对象

// 两种形式定义对象
// 文字语法形式
var value = 1;
var myObj = {
  key: value,
  // ...
};
// 构造形式
var myObj = new Object();
myObj.key = value;

// js有 string number boolean null undefined object symbol 六种主要类型
// typeof null 返回 object，但是null是基本类型
// 在js中二进制前三位都为0 会被判断为 object 类型 null的二进制表示为全0 所有会返回object

// 必要时语言会自动把字符串字面量转换成String对象，不需要再去创建显示对象也能访问属性和方法
var strPrimitive = "i am a string";
console.log(strPrimitive.length); // 13
console.log(strPrimitive.charAt(3)); // 'm'
// 同样--> 数值字面量也是这样的
// null和undefined 没有构造形式，只有文字形式

// 属性访问 键访问区别 .操作符要求属性名满足标识符的命名规范， []语法可以接受任意的UTF-8/Unicode字符串作为属性名
var myObj = { a: 2 };
myObj.a; // 2, 属性访问
myObj["a"]; // 2 键访问

// 在对象中，属性名永远是字符串
var myObject = {};
myObject[true] = "foo";
myObject[3] = "bar";
myObject[myObject] = "baz";

console.log(myObject["myObject"]); // undefined
console.log(myObject["[object Object]"]); // "baz"

// 可计算属性名
var prefix = "foo";
var myObj = {
  [prefix + "bar"]: "hello",
  [prefix + "baz"]: "world",
};
console.log(myObj["foobar"]);
console.log(myObj["foobaz"]);

// 属性和方法 从技术角度来说，函数永远不会属于一个对象，只是对于函数对象的引用

// 数组 支持[]访问 数组也是对象，每个下标都是整数，但是也可以给数组添加属性
var list = ["foo", 12, "bar"];
list.baz = "baz"; // 但是通常不这么用

// 复制对象
// 深拷贝
var someObj = { a: 1, b: 2 };
var newObj = JSON.parse(JSON.stringify(someObj));
// 浅拷贝 ES6定义了Object.assign()来实现
// 第一个参数是目标对象，之后跟 一个或多个源对象，它会遍历源对象所有可枚举的自有键，并复制到目标对象
var newObj = Object.assign({}, myObject);

// 属性描述符 ES5，所有属性具备了属性描述符
var myObject = { a: 2 };
// 以下是返回属性描述符的两种方法
console.log(Object.getOwnPropertyDescriptor(myObject, "a"));
console.log(Object.getOwnPropertyDescriptors(myObject));
var myObj = {};
// defineProperty() 给myObj添加了一个普通的属性并显式指定了特性
// writable 是否可以修改属性值 置为false时 属性不可以被改变
// configurable 只要属性可配置 为true，就可以使用defineProperty() 来修改属性描述符，但是值为false时，不能使用defineProperty()
// 修改configurable 是单向操作，无法撤销， configurable:false 还会禁止删除这个属性
// enumerable 在for..in循环中，把属性的enumerable置为false，这个属性就不会出现在枚举中
Object.defineProperty(myObj, "a", {
  value: 2,
  writable: true,
  configurable: false,
  enumerable: true,
});
console.log(myObj.a);
delete myObj.a;
console.log(myObj.a);

// 对象常量 writable:false  configurable:false 就可以创建一个真正的常量属性
// 禁止扩展 禁止一个对象添加新属性并且保留已有属性 使用 Object.preventExtensions()
var myObj = { a: 2 };
Object.preventExtensions(myObj);
myObj.b = 3;
console.log(myObj.b); // undefined
// 密封 Object.seal(..) 会创建一个密封对象，实际上是在一个对象上调用Object.preventExtensions(..)并把所有现有属性标记为configurable:false
// 冻结 Object.freeze(..) 创建一个冻结对象，实际上是在一个对象上调用Object.seal(..)并把所有'数据访问'属性标记为writable:false

// [[Get]] 第五章 原型会讲
// [[Put]]
// 如果存在这个属性
// 1.属性是否是访问描述符？ 是并存在setter就调用setter
// 2.属性的数据描述符writable是否是false，是的话静默失败，严格模式下有TypeError异常
// 3.都不是，将该值设为属性的值

// Getter Setter
// 都是隐藏函数，获取/设置属性值时调用
// 当给一个属性定义getter setter或者两者都有时，这个属性会被定义为"访问描述符"
// 通常来说getter setter是成对出现的，setter会覆盖单个属性默认的[[Put]]操作(赋值操作)
var myObj = {
  // 给a定义一个getter
  get a() {
    return this._a;
  },
  // 给a定义一个setter
  set a(val) {
    this._a = val * 2;
  },
};
myObj.a = 4;
console.log(myObj.a);

// 存在性
// myObj.a 的属性返回值可能是undefined 但是不能确定 是属性中存储的undefined，还是因为不存在所以返回undefined
var myObj = { a: 2 };
"a" in myObj; // true   in操作符会检查属性是否在对象及其[[Prototype]]原型链中
"b" in myObj; // false
myObj.hasOwnProperty("a"); // true  hasOwnProperty只会检查属性是否在myObj中，不会检查[[Prototype]]链
myObj.hasOwnProperty("b"); // false

// 枚举 可枚举 === 可以出现在对象属性的遍历中
var myObj = {};
Object.defineProperty(myObj, "a", {
  enumerable: true,
  value: 2,
});
Object.defineProperty(myObj, "b", {
  enumerable: false,
  value: 3,
});
// for..in 可区分属性是否可枚举
for (var k in myObj) {
  console.log(k, myObj[k]);
}
myObj.propertyIsEnumerable("a"); // true 会检查给定的属性名是否直接存在于对象中（不是原型链）并且满足enumerable:true
myObj.propertyIsEnumerable("b"); // false

Object.keys(myObj); // ["a"] 返回数组，包含所有可枚举属性
Object.getOwnPropertyNames(myObj); // ["a", "b"]，返回数组，包含所有属性，无论是否可枚举

// 遍历
// for..in遍历对象是无法直接获取属性值的，实际上是遍历对象中所有可枚举的属性
// for..of ES6增加 遍历数组 (对象本身定义了迭代器的话也可以遍历对象)
// for..of 首先会向被访问对象请求一个迭代器对象，然后通过迭代器对象的next()方法来遍历所有返回值
// 数组内置有@@iterator
var myArray = [1, 2, 3];
var it = myArray[Symbol.iterator]();
console.log(it.next(), it.next());
for (var i of myArray) {
  console.log(i);
}
// 在对象中可以自定义迭代器@@iterator
var myObj = { a: 2 };
Object.defineProperty(myObj, Symbol.iterator, {
  enumerable: false,
  writable: false,
  configurable: true,
  value: function () {
    var that = this;
    var idx = 0;
    var ks = Object.keys(that); // 返回属性数组
    return {
      next: function () {
        return {
          value: that[ks[idx++]],
          done: idx > ks.length,
        };
      },
    };
  },
});
var it = myObj[Symbol.iterator]();
console.log(it.next());
console.log(it.next());

// 小结
// 对象形式 对象类型 对象键值对 属性描述符 访问描述符getter setter 遍历
